Grammars for Building Container Images

Table of Contents

ContainerFile and DockerFile are almost the same, the difference between which is simply that the former is the default for podman, while the latter is the default for docker.

1. Basic Structure of an Image Build

The container image is constructed within 5 stages: we start from base image, then install packages inside this image, copy application-specific files, configuration necessary environment (e.g., environment variables, settings up toolchains), and define the startup command.

Since we always start from base image, this design actually kind of defines a fixed procedure of “how to transform the image”, making the product reproducible.

2. Essential Commands

FROM <image[:tag]>

We can use the FROM command to configure the base image to build on. For example, FROM ubuntu:24.04 tells podman it should build on top of Ubuntu 24.04.

Moreover, we can use multiple FROM ... AS statements, so that following FROM statements can use the results produced in former FROM statements.

RUN <command>
Executes commands during build. If <command> is normal executable bash command, then it will be executed by /bin/sh; if <command> is a list of strings, then it will involve shell to execute.
Layer
Each statement creates a “layer” of the image. Docker images are a stack of immutable layers, each command “transforms” the previous layer into a new layer.
COPY <source> <destination>

Copy some file or directory into other location. We can copy files as another file, copy file to directory, or copy directory as another directory.

    # copy file to file
    COPY app.py /app/app.py

    # copy folder to folder
    COPY src/ /app/src/

    # Use dot to copy everything
    COPY . /app
ADD <source> <destination>
An extension to COPY that can automatically extract archives, and support downloading from URLs.
WORKDIR <path>
Set working directory. This means that, the following relative file paths will be relative to this working directory.
ENV <key=value>
Sets global environment variables.
ARG <key=value>
Build-time variables. These environment variables are only available during docker build.
EXPOSE <port>
It means the application listens on in-image port. It does not publish the port. This should be done via setting -p <pub-port>:<in-image-port> instead through docker CLI or configuration files.
CMD <command>
Defines what happens when the container starts. Similar to RUN, the <command> can also be a list of strings. But unlike RUN, it’s recommended to use list of commands for CMD because the signals can be received by apps correctly, instead of agented by shell.
ENTRYPOINT <command-as-list>
This command fixes the executable used in later CMD. For example, ENTRYPOINT ["python3"]; CMD ["app.py"] executes python3 app.py
USER <username>
Change the default user (root). Should first create a user using RUN useradd ...
VOLUME <path>
Sets up an in-container folder which is indeed an external folder, so that data in this “internal” folder can be retrieved by external system. Useful for databases, logs, user data.
LABEL <key=value>
Just configures the metadata of the image itself.
.dockerignore or .containerignore
Similar functionality as .gitignore, useful when copying the entire directory yet to exclude a few.
HEALTHCHECK CMD <command>
Defines how the container is regarded as healthy. Docker periodically runs the <command>, the container becomes unhealthy on healthcheck failure.

Date: 2026-08-05 Wed

Author: ArcaLunar